﻿using System;
using System.Data.SqlClient;
using System.Linq.Expressions;
using PropertyExpression.Common;

namespace PropertyExpression.ControllableQuery
{
    public static class DataReaderExtensions
    {
        public static TProperty GetValue<T, TProperty>(this IDataReader<T> it, Expression<Func<T, TProperty>> propertyExpression, Func<object, TProperty> func)
        {
            return func(it.SqlDataReader[((MemberExpression) propertyExpression.Body).Member.Name]);
        }

        public static string GetValue<T>(this IDataReader<T> it, Expression<Func<T, string>> propertyExpression)
        {
            return it.GetValue(propertyExpression, StringConverter);
        }

        private static Func<object, string> StringConverter
        {
            get { return value => value == DBNull.Value ? null : (string)value; }
        }

        public static Guid GetValue<T>(this IDataReader<T> it, Expression<Func<T, Guid>> propertyExpression)
        {
            return it.GetValue(propertyExpression, GuidConverter);
        }

        private static Func<object, Guid> GuidConverter
        {
            get { return value => (Guid) value; }
        }

        public static decimal GetValue<T>(this IDataReader<T> it, Expression<Func<T, decimal>> propertyExpression)
        {
            return it.GetValue(propertyExpression, DecimalConverter);
        }

        private static Func<object, decimal> DecimalConverter
        {
            get { return value => (decimal)value; }
        }

        public static int GetValue<T>(this IDataReader<T> it, Expression<Func<T, int>> propertyExpression)
        {
            return it.GetValue(propertyExpression, IntConverter);
        }

        public static int? GetValue<T>(this IDataReader<T> it, Expression<Func<T, int?>> propertyExpression)
        {
            return it.GetValue(propertyExpression, NullableIntConverter);
        }

        private static Func<object, int> IntConverter
        {
            get { return value => (int)value; }
        }

        private static Func<object, long> LongConverter
        {
            get { return value => (long)value; }
        }

        private static Func<object, byte[]> BytesConverter
        {
            get { return value => (byte[])value; }
        }

        private static Func<object, int?> NullableIntConverter
        {
            get { return value =>  value==DBNull.Value? new int?():IntConverter(value); }
        }

        private static Func<object, Guid?> NullableGuidConverter
        {
            get { return value => value == DBNull.Value ? new Guid?() : GuidConverter(value); }
        }

        private static Func<object, float> FloatConverter
        {
            get { return value => (float)(double)value; }
        }

        private static Func<object, float?> NullableFloatConverter
        {
            get { return value => value == DBNull.Value ? new float?() : FloatConverter(value); }
        }

        public static DateTime GetValue<T>(this IDataReader<T> it, Expression<Func<T, DateTime>> propertyExpression)
        {
            return it.GetValue(propertyExpression, DateTimeConverter);
        }

        private static Func<object, DateTime> DateTimeConverter
        {
            get { return value => (DateTime)value; }
        }

        public static DateTime? GetValue<T>(this IDataReader<T> it, Expression<Func<T, DateTime?>> propertyExpression)
        {
            return it.GetValue(propertyExpression, NullableDateTimeConverter);
        }

        private static Func<object, DateTime?> NullableDateTimeConverter
        {
            get { return value => value==DBNull.Value? new DateTime?():DateTimeConverter(value); }
        }

        private static Func<object, Decimal?> NullableDecimalConverter
        {
            get { return value => value == DBNull.Value ? new Decimal?() : DecimalConverter(value); }
        }

        public static bool GetValue<T>(this IDataReader<T> it, Expression<Func<T, bool>> propertyExpression)
        {
            return it.GetValue(propertyExpression, BooleanConverter);
        }

        private static Func<object, bool> BooleanConverter
        {
            get { return value => (bool)value; }
        }

        private static Func<object, bool?> NullableBooleanConverter
        {
            get { return value => value==DBNull.Value?new bool?():BooleanConverter(value); }
        }

        public static Option<TProperty> GetValue<T, TProperty>(this IDataReader<T> it, Expression<Func<T, Option<TProperty>>> propertyExpression)
        {
            return it.GetValue(propertyExpression, value => value == DBNull.Value ? new Option<TProperty>() : (TProperty)value);
        }

        public static Guid GetGuid(SqlDataReader reader, string fieldName)
        {
            return GuidConverter(reader[fieldName]);
        }

        public static Guid? GetNullableGuid(SqlDataReader reader, string fieldName)
        {
            return NullableGuidConverter(reader[fieldName]);
        }

        public static string GetString(SqlDataReader reader, string fieldName)
        {
            return StringConverter(reader[fieldName]);
        }

        public static int GetInt(SqlDataReader reader, string fieldName)
        {
            return IntConverter(reader[fieldName]);
        }

        public static long GetLong(SqlDataReader reader, string fieldName)
        {
            return LongConverter(reader[fieldName]);
        }

        public static byte[] GetBytes(SqlDataReader reader, string fieldName)
        {
            return BytesConverter(reader[fieldName]);
        }

        public static int? GetNullableInt(SqlDataReader reader, string fieldName)
        {
            return NullableIntConverter(reader[fieldName]);
        }

        public static int? SingleGetNullableInt(SqlDataReader reader)
        {
            return NullableIntConverter(reader[0]);
        }

        public static decimal? SingleGetNullableDecimal(SqlDataReader reader)
        {
            return NullableDecimalConverter(reader[0]);
        }

        public static Guid SingleGetGuid(SqlDataReader reader)
        {
            return GuidConverter(reader[0]);
        }

        public static string SingleGetString(SqlDataReader reader)
        {
            return StringConverter(reader[0]);
        }

        public static decimal GetDecimal(SqlDataReader reader, string fieldName)
        {
            return DecimalConverter(reader[fieldName]);
        }

        public static DateTime GetDateTime(SqlDataReader reader, string fieldName)
        {
            return DateTimeConverter(reader[fieldName]);
        }

        public static DateTime? GetNullableDateTime(SqlDataReader reader, string fieldName)
        {
            return NullableDateTimeConverter(reader[fieldName]);
        }

        public static bool GetBoolean(SqlDataReader reader, string fieldName)
        {
            return BooleanConverter(reader[fieldName]);
        }

        public static bool? GetNullableBoolean(SqlDataReader reader, string fieldName)
        {
            return NullableBooleanConverter(reader[fieldName]);
        }

        public static float GetFloat(SqlDataReader reader, string fieldName)
        {
            return FloatConverter(reader[fieldName]);
        }

        public static float? GetNullableFloat(SqlDataReader reader, string fieldName)
        {
            return NullableFloatConverter(reader[fieldName]);
        }

        public static T Materialize<T>(this IDataReader<T> reader)
        {
            return Materialize<T>(reader.SqlDataReader);
        }

        public static T Materialize<T>(this SqlDataReader reader)
        {
            return Cache<T>.Func(reader);
        }

        private static class Cache<T>
        {
            public static readonly Func<SqlDataReader, T> Func;

            static Cache()
            {
                Func = DynamicMaterializer.Build<SqlDataReader, T>(
                    type =>
                        {
                            if (type == typeof (Guid))
                            {
                                return typeof (DataReaderExtensions).GetMethod("GetGuid");
                            }
                            else if (type == typeof(Guid?))
                            {
                                return typeof(DataReaderExtensions).GetMethod("GetNullableGuid");
                            }
                            else if (type == typeof (string))
                            {
                                return typeof (DataReaderExtensions).GetMethod("GetString");
                            }
                            else if (type == typeof (int))
                            {
                                return typeof (DataReaderExtensions).GetMethod("GetInt");
                            }
                            else if (type == typeof(long))
                            {
                                return typeof(DataReaderExtensions).GetMethod("GetLong");
                            }
                            else if (type == typeof(int?))
                            {
                                return typeof (DataReaderExtensions).GetMethod("GetNullableInt");
                            }
                            else if (type == typeof (decimal))
                            {
                                return typeof (DataReaderExtensions).GetMethod("GetDecimal");
                            }
                            else if (type == typeof (DateTime))
                            {
                                return typeof (DataReaderExtensions).GetMethod("GetDateTime");
                            }
                            else if (type == typeof (DateTime?))
                            {
                                return typeof (DataReaderExtensions).GetMethod("GetNullableDateTime");
                            }
                            else if (type == typeof (bool))
                            {
                                return typeof (DataReaderExtensions).GetMethod("GetBoolean");
                            }
                            else if (type == typeof (bool?))
                            {
                                return typeof (DataReaderExtensions).GetMethod("GetNullableBoolean");
                            }
                            else if (type == typeof (float))
                            {
                                return typeof (DataReaderExtensions).GetMethod("GetFloat");
                            }
                            else if (type == typeof (float?))
                            {
                                return typeof (DataReaderExtensions).GetMethod("GetNullableFloat");
                            }
                            else if (type == typeof(byte[]))
                            {
                                return typeof(DataReaderExtensions).GetMethod("GetBytes");
                            }
                            else
                            { 
                                throw new NotImplementedException();
                            }
                        },
                    type => {
                        if (type == typeof (int?))
                            return typeof(DataReaderExtensions).GetMethod("SingleGetNullableInt");
                        if (type == typeof(Guid))
                            return typeof(DataReaderExtensions).GetMethod("SingleGetGuid");
                        if (type == typeof(string))
                            return typeof(DataReaderExtensions).GetMethod("SingleGetString");
                        if (type == typeof(decimal?))
                            return typeof(DataReaderExtensions).GetMethod("SingleGetNullableDecimal");
                        else
                        {
                            //TODO: добавить остальные типы
                            throw new NotImplementedException();
                        }
                    });
            }
        }
    }
}